package io.renren.config;

import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.PropertyAccessor;
import com.fasterxml.jackson.databind.ObjectMapper;
import io.renren.common.utils.SpringContextUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.cache.Cache;
import org.springframework.cache.CacheManager;
import org.springframework.cache.annotation.EnableCaching;
import org.springframework.cache.ehcache.EhCacheCacheManager;
import org.springframework.cache.ehcache.EhCacheManagerFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.cache.RedisCacheConfiguration;
import org.springframework.data.redis.cache.RedisCacheManager;
import org.springframework.data.redis.cache.RedisCacheWriter;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.serializer.Jackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.RedisSerializationContext;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.time.Duration;
import java.util.HashMap;
import java.util.Map;

/**
 * 缓存配置
 * 策略:
 * 1. 默认ehcache
 * 2. 如果配置renren.redis.open 则使用redis
 */
@Configuration
@EnableCaching
public class CacheConfig {

    private static Logger log = LoggerFactory.getLogger(CacheConfig.class);

    /**
     * 配置renren.redis.open时使用RedisCacheManager，否则使用EhCacheCacheManager
     */
    @Bean
    @ConditionalOnProperty(prefix = "renren.redis", name = "open", havingValue = "false", matchIfMissing = false)
    public EhCacheManagerFactoryBean ehCacheManagerFactoryBean() {
        EhCacheManagerFactoryBean cacheManagerFactoryBean = new EhCacheManagerFactoryBean();
        cacheManagerFactoryBean.setConfigLocation(new ClassPathResource("ehcache.xml"));
        cacheManagerFactoryBean.setShared(true);
        return cacheManagerFactoryBean;
    }

    @Bean
    @ConditionalOnBean(EhCacheManagerFactoryBean.class)
    public EhCacheCacheManager ehCacheCacheManager(EhCacheManagerFactoryBean ehCacheManagerFactoryBean) {
        if (log.isDebugEnabled()) {
            log.debug("renren.redis.open = false 使用EhCache事务缓存");
        }
        return new EhCacheCacheManager(ehCacheManagerFactoryBean.getObject()) {
            @Override
            protected Cache getMissingCache(String name) {
                Cache cache = super.getMissingCache(name);
                if (cache == null && getCacheManager() != null) {
                    //使用default配置克隆缓存
                    getCacheManager().addCacheIfAbsent(name);
                    cache = super.getCache(name);
                }
                return cache;
            }
        };
    }

    /**
     * 配置renren.redis.open时使用RedisCacheManager，否则使用EhCacheCacheManager
     */
    @Bean
    @ConditionalOnProperty(prefix = "renren.redis", name = "open", havingValue = "true", matchIfMissing = true)
    public RedisCacheManager cacheManager(RedisConnectionFactory redisConnectionFactory) {
        if (log.isDebugEnabled()) {
            log.debug("renren.redis.open = true 使用Redis事务缓存");
        }
        return new RedisCacheManager(
                RedisCacheWriter.nonLockingRedisCacheWriter(redisConnectionFactory),
                // 默认策略，未配置的 key 会使用这个
                this.getRedisCacheConfigurationWithTtl(60),
                // 指定 key 策略
                this.getRedisCacheConfigurationMap()
        );
    }

    /**
     * Description: redis缓存配置，注释@Cacheable使用的缓存名在此配置
     *
     * @author: afeng
     * @date: 2020/2/23 11:02
     */
    private Map<String, RedisCacheConfiguration> getRedisCacheConfigurationMap() {
        Map<String, RedisCacheConfiguration> redisCacheConfigurationMap = new HashMap<>();
        //自定义设置缓存名与过期时间
        redisCacheConfigurationMap.put("userCache", this.getRedisCacheConfigurationWithTtl(60 * 60 * 60 * 2));

        return redisCacheConfigurationMap;
    }

    /**
     * Description: redis缓存配置模板
     *
     * @author: afeng
     * @date: 2020/2/23 11:02
     */
    private RedisCacheConfiguration getRedisCacheConfigurationWithTtl(Integer seconds) {
        Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.enableDefaultTyping(ObjectMapper.DefaultTyping.NON_FINAL);
        jackson2JsonRedisSerializer.setObjectMapper(om);
        RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig();
        redisCacheConfiguration = redisCacheConfiguration.serializeValuesWith(
                RedisSerializationContext
                        .SerializationPair
                        .fromSerializer(jackson2JsonRedisSerializer)
        ).entryTtl(Duration.ofSeconds(seconds));

        return redisCacheConfiguration;
    }


    /**
     * 动态配置缓存
     * 示例：<code>Cache fiveMinutes = CacheConfig.dynaConfigCache("5min", 300);</code>
     */
    @SuppressWarnings("unchecked")
    public static Cache dynaConfigCache(String name, long timeToLiveSeconds) {
        CacheManager cacheManager = SpringContextUtils.getBean(CacheManager.class);
        if (cacheManager instanceof RedisCacheManager) {
            if (log.isDebugEnabled()) {
                log.debug("使用RedisCacheManager");
            }
            Field expiresField = ReflectionUtils.findField(RedisCacheManager.class, "expires");
            ReflectionUtils.makeAccessible(expiresField);
            Map<String, Long> expires = (Map<String, Long>) ReflectionUtils.getField(expiresField, cacheManager);
            if (expires == null) {
                ReflectionUtils.setField(expiresField, cacheManager, expires = new HashMap<>());
            }
            expires.put(name, timeToLiveSeconds);
        } else if (cacheManager instanceof EhCacheCacheManager) {
            if (log.isDebugEnabled()) {
                log.debug("使用EhCacheCacheManager");
            }
            net.sf.ehcache.Cache ehCacheCache = (net.sf.ehcache.Cache) cacheManager.getCache(name).getNativeCache();
            net.sf.ehcache.config.CacheConfiguration cacheConfiguration = ehCacheCache.getCacheConfiguration();
            cacheConfiguration.timeToLiveSeconds(timeToLiveSeconds);
        }
        return cacheManager.getCache(name);
    }
}